package com.mkjb.utils;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MybatisPageInterceptor implements Interceptor {

    private ThreadLocal<Page> localPage = new ThreadLocal<Page>();

    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();

        if (target instanceof StatementHandler) {
            if (target instanceof RoutingStatementHandler) {
                target = getObjectFieldValue(target, "delegate");
            }

            BaseStatementHandler statementHandler = (BaseStatementHandler) target;
            MappedStatement mappedStatement = (MappedStatement) getObjectFieldValue(statementHandler, "mappedStatement");
            BoundSql boundSql = statementHandler.getBoundSql();
            Object parameterObject = boundSql.getParameterObject();

            if (parameterObject instanceof Page) {
                Page page = (Page) parameterObject;
                localPage.set(page);
                long totalCount = getTotalCountFormDatabase(boundSql.getSql(), mappedStatement, boundSql);
                page.setTotalPage(totalCount);

                setBoundSql(boundSql, page);
            }
        }
        return invocation.proceed();
    }

    private Object getObjectFieldValue(Object target, String filedName) throws NoSuchFieldException, IllegalAccessException {
        Field field = getField(target, filedName);
        return field.get(target);
    }

    private void setObjectFieldValue(Object target, String filedName, Object newValue) throws NoSuchFieldException, IllegalAccessException {
        Field field = getField(target, filedName);
        field.set(target, newValue);
    }

    private Field getField(Object target, String filedName) throws NoSuchFieldException {
        Field field = ReflectionUtils.findField(target.getClass(), filedName);
        if (field == null) {
            throw new NoSuchFieldException("在" + target.getClass() + "上未找到属性:" + filedName);
        }
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        return field;
    }

    private void setBoundSql(BoundSql boundSql, Page page) throws NoSuchFieldException, IllegalAccessException {
        setObjectFieldValue(boundSql, "sql", buildSql(boundSql.getSql(), page));
    }

    private String buildSql(String sql, Page page) {
        return sql + " limit " + page.getStartPosition() + "," + page.getRowSize();
    }

    private long getTotalCountFormDatabase(String sql, MappedStatement mappedStatement, BoundSql boundSql) {
        String countSql = getCountSql(sql);
        Connection connection = null;
        ResultSet rs = null;
        PreparedStatement countStmt = null;
        long totalCount = 0;
        try {
            connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
            countStmt = connection.prepareStatement(countSql);
            DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,
                    boundSql.getParameterObject(), boundSql);
            parameterHandler.setParameters(countStmt);
            rs = countStmt.executeQuery();
            if (rs.next()) {
                totalCount = rs.getLong("totalCount");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (countStmt != null) {
                    countStmt.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return totalCount;
    }

    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    public void setProperties(Properties properties) {
        //notching
    }

    private String getCountSql(String sql) {
        return "SELECT COUNT(*) as totalCount FROM (" + sql + ") aliasForPage";
    }
}
